// This file is part of Keepass2Android, Copyright 2025 Philipp Crocoll.
//
//   Keepass2Android is free software: you can redistribute it and/or modify
//   it under the terms of the GNU General Public License as published by
//   the Free Software Foundation, either version 3 of the License, or
//   (at your option) any later version.
//
//   Keepass2Android is distributed in the hope that it will be useful,
//   but WITHOUT ANY WARRANTY; without even the implied warranty of
//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   GNU General Public License for more details.
//
//   You should have received a copy of the GNU General Public License
//   along with Keepass2Android.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Globalization;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Widget;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using KeePassLib;
using KeePassLib.Security;
using KeePassLib.Utility;
using KeeTrayTOTP.Libraries;
using Android.Content.Res;
using Android.Preferences;
using Google.Android.Material.Dialog;
using keepass2android;
using PluginTOTP;

namespace keepass2android
{
  /// <summary>
  /// Interface for data stored in an intent or bundle as extra string
  /// </summary>
  public interface IExtra
  {
    /// <summary>
    /// put data to a bundle by calling one of the PutXX methods
    /// </summary>
    void ToBundle(Bundle b);

    /// <summary>
    /// Put data to an intent by calling PutExtra
    /// </summary>
    void ToIntent(Intent i);
  }

  /// <summary>
  /// represents data stored in an intent or bundle as extra string
  /// </summary>
  public class StringExtra : IExtra
  {
    public string Key { get; set; }
    public string Value { get; set; }

    #region IExtra implementation

    public void ToBundle(Bundle b)
    {
      b.PutString(Key, Value);
    }

    public void ToIntent(Intent i)
    {
      i.PutExtra(Key, Value);
    }

    #endregion
  }

  /// <summary>
  /// represents data stored in an intent or bundle as extra string array list
  /// </summary>
  public class StringArrayListExtra : IExtra
  {
    public string Key { get; set; }
    public IList<string> Value { get; set; }

    #region IExtra implementation

    public void ToBundle(Bundle b)
    {
      b.PutStringArrayList(Key, Value);
    }

    public void ToIntent(Intent i)
    {
      i.PutStringArrayListExtra(Key, Value);
    }

    #endregion
  }

  /// <summary>
  /// represents data stored in an intent or bundle as extra int
  /// </summary>
  public class IntExtra : IExtra
  {
    public string Key { get; set; }
    public int Value { get; set; }

    #region IExtra implementation

    public void ToBundle(Bundle b)
    {
      b.PutInt(Key, Value);
    }

    public void ToIntent(Intent i)
    {
      i.PutExtra(Key, Value);
    }

    #endregion
  }

  /// <summary>
  /// represents data stored in an intent or bundle as extra bool
  /// </summary>
  public class BoolExtra : IExtra
  {
    public string Key { get; set; }
    public bool Value { get; set; }

    #region IExtra implementation

    public void ToBundle(Bundle b)
    {
      b.PutBoolean(Key, Value);
    }

    public void ToIntent(Intent i)
    {
      i.PutExtra(Key, Value);
    }

    #endregion
  }


  /// <summary>
  /// represents data stored in an intent or bundle as extra string array
  /// </summary>
  public class StringArrayExtra : IExtra
  {
    public string Key { get; set; }
    public string[] Value { get; set; }

    #region IExtra implementation

    public void ToBundle(Bundle b)
    {
      b.PutStringArray(Key, Value);
    }

    public void ToIntent(Intent i)
    {
      i.PutExtra(Key, Value);
    }

    #endregion
  }

  /// <summary>
  /// base class for "tasks": these are things the user wants to do and which require several activities
  /// </summary>
  /// Therefore AppTasks need to be serializable to bundles and intents to "survive" saving to instance state and changing activities.
  /// An AppTask has a type and may have several parameters ("extras").
  /// Activities call the task at special points so tasks can change the behaviour at these points.
  public abstract class AppTask
  {
    /// <summary>
    /// Loads the parameters of the task from the given bundle
    /// </summary>
    public virtual void Setup(Bundle b)
    {
      CanActivateSearchViewOnStart = b.GetBoolean(CanActivateSearchViewOnStartKey, true);

    }

    public const String CanActivateSearchViewOnStartKey = "CanActivateSearchViewOnStart";

    /// <summary>
    /// Can be overwritten to indicate that it is not desired to bring up the search view when starting a groupactivity
    /// </summary>
    public virtual bool CanActivateSearchViewOnStart
    {
      get;
      set;
    }


    /// <summary>
    /// Returns the parameters of the task for storage in a bundle or intent
    /// </summary>
    /// <value>The extras.</value>
    public virtual IEnumerable<IExtra> Extras
    {
      get
      {
        yield return new BoolExtra { Key = CanActivateSearchViewOnStartKey, Value = CanActivateSearchViewOnStart };
      }
    }

    public virtual void LaunchFirstGroupActivity(Activity act)
    {
      GroupActivity.Launch(act, this, new ActivityLaunchModeRequestCode(0));
    }

    public virtual void AfterAddNewEntry(EntryEditActivity entryEditActivity, PwEntry newEntry)
    {
    }


    public virtual void PrepareNewEntry(PwEntry newEntry)
    {

    }

    public const String AppTaskKey = "KP2A_APPTASK";

    /// <summary>
    /// Should be used in OnCreate to (re)create a task
    /// if savedInstanceState is not null, the task is recreated from there. Otherwise it's taken from the intent.
    /// </summary>
    public static AppTask GetTaskInOnCreate(Bundle savedInstanceState, Intent intent)
    {
      AppTask task;
      if (savedInstanceState != null)
      {
        task = CreateFromBundle(savedInstanceState);
      }
      else
      {
        task = CreateFromIntent(intent);
      }
      Kp2aLog.Log("Loaded task " + task);
      return task;
    }

    public static AppTask CreateFromIntent(Intent i)
    {
      return CreateFromBundle(i.Extras);
    }

    public static AppTask CreateFromBundle(Bundle b)
    {
      return CreateFromBundle(b, new NullTask());
    }

    public static AppTask CreateFromBundle(Bundle b, AppTask failureReturn)
    {
      if (b == null)
        return failureReturn;

      string taskType = b.GetString(AppTaskKey);

      if (string.IsNullOrEmpty(taskType))
        return failureReturn;

      try
      {
        Type type = Type.GetType("keepass2android." + taskType);
        if (type == null)
          return failureReturn;
        AppTask task = (AppTask)Activator.CreateInstance(type);
        task.Setup(b);
        return task;
      }
      catch (Exception e)
      {
        Kp2aLog.Log("Cannot convert " + taskType + " in task: " + e);
        return failureReturn;
      }

    }

    /// <summary>
    /// Adds the extras of the task to the intent
    /// </summary>
    public void ToIntent(Intent intent)
    {
      GetTypeExtra(GetType()).ToIntent(intent);

      foreach (IExtra extra in Extras)
      {
        extra.ToIntent(intent);
      }
    }

    /// <summary>
    /// Adds the extras of the task to the bundle
    /// </summary>
    public void ToBundle(Bundle bundle)
    {
      GetTypeExtra(GetType()).ToBundle(bundle);

      foreach (IExtra extra in Extras)
      {
        extra.ToBundle(bundle);
      }

    }

    /// <summary>
    /// Returns an IExtra which must be part of the Extras of a task to describe the type
    /// </summary>
    static IExtra GetTypeExtra(Type type)
    {
      return new StringExtra { Key = AppTaskKey, Value = type.Name };
    }

    public virtual void StartInGroupActivity(GroupBaseActivity groupBaseActivity)
    {
    }

    public virtual void SetupGroupBaseActivityButtons(GroupBaseActivity groupBaseActivity)
    {
      groupBaseActivity.SetupNormalButtons();
    }

    public void SetActivityResult(Activity activity, Result result)
    {
      Intent data = new Intent();
      ToIntent(data);
      activity.SetResult(result, data);
    }

    /// <summary>
    /// Tries to extract the task from the data given as an Intent object in OnActivityResult. If successful, the task is assigned,
    /// otherwise, false is returned.
    /// </summary>
    public static bool TryGetFromActivityResult(Intent data, ref AppTask task)
    {
      if (data == null)
      {
        Kp2aLog.Log("TryGetFromActivityResult: no data");
        return false;
      }
      AppTask tempTask = CreateFromBundle(data.Extras, null);
      if (tempTask == null)
      {
        Kp2aLog.Log("No AppTask in OnActivityResult");
        return false;
      }

      task = tempTask;
      Kp2aLog.Log("AppTask " + task + " in OnActivityResult");
      return true;
    }

    protected void RemoveTaskFromIntent(Activity act)
    {
      if (act.Intent != null)
        act.Intent.RemoveExtra(AppTaskKey);

    }

    public virtual void CompleteOnCreateEntryActivity(EntryActivity activity, Thread notifyPluginsOnOpenThread)
    {
      //this default implementation is executed when we're opening an entry manually, i.e. without search/autofill.
      //We only activate the keyboard if this is enabled in "silent mode"
      ISharedPreferences prefs = PreferenceManager.GetDefaultSharedPreferences(activity);
      bool activateKeyboard = prefs.GetBoolean("kp2a_switch_rooted", false) &&
                              !prefs.GetBoolean(
                                  activity.GetString(Resource.String
                                      .OpenKp2aKeyboardAutomaticallyOnlyAfterSearch_key), false);

      activity.StartNotificationsService(activateKeyboard);
    }

    public virtual void PopulatePasswordAccessServiceIntent(Intent intent)
    {

    }


    protected static bool GetBoolFromBundle(Bundle b, string key, bool defaultValue)
    {
      bool boolValue;
      string stringValue = b.GetString(key);
      if (!Boolean.TryParse(stringValue, out boolValue))
      {
        boolValue = defaultValue;
      }
      return boolValue;
    }

    protected static int GetIntFromBundle(Bundle b, string key, int defaultValue)
    {
      int intValue;
      var strValue = b.GetString(key);
      if (!Int32.TryParse(strValue, out intValue))
      {
        intValue = defaultValue;
      }
      return intValue;
    }
  }

  /// <summary>
  /// Implementation of AppTask for "no task currently active" (Null pattern)
  /// </summary>
  public class NullTask : AppTask
  {

  }

  /// <summary>
  /// User is about to search an entry for a given URL
  /// </summary>
  /// Derive from SelectEntryTask. This means that as soon as an Entry is opened, we're returning with
  /// ExitAfterTaskComplete. This also allows to specify the flag if we need to display the user notifications.
  public class SearchUrlTask : SelectEntryTask
  {
    public SearchUrlTask()
    {
      AutoReturnFromQuery = true;
    }

    public const String UrlToSearchKey = "UrlToSearch";
    public const String AutoReturnFromQueryKey = "AutoReturnFromQuery";

    public string UrlToSearchFor
    {
      get;
      set;
    }

    public override void Setup(Bundle b)
    {
      base.Setup(b);
      UrlToSearchFor = b.GetString(UrlToSearchKey);
      AutoReturnFromQuery = b.GetBoolean(AutoReturnFromQueryKey, true);
    }
    public override IEnumerable<IExtra> Extras
    {
      get
      {
        foreach (IExtra e in base.Extras)
          yield return e;
        yield return new StringExtra { Key = UrlToSearchKey, Value = UrlToSearchFor };
        yield return new BoolExtra { Key = AutoReturnFromQueryKey, Value = AutoReturnFromQuery };
      }
    }



    public bool AutoReturnFromQuery { get; set; }

    public override void LaunchFirstGroupActivity(Activity act)
    {
      if (String.IsNullOrEmpty(UrlToSearchFor))
      {
        GroupActivity.Launch(act, new SelectEntryTask()
        {
          ShowUserNotifications = ShowUserNotifications,
          CopyTotpToClipboard = CopyTotpToClipboard,
          ActivateKeyboard = ActivateKeyboard
        },
            new ActivityLaunchModeRequestCode(0));
      }
      else
      {
        ShareUrlResults.Launch(act, this, new ActivityLaunchModeRequestCode(0));
      }

      //removed. this causes an issue in the following workflow:
      //When the user wants to find an entry for a URL but has the wrong database open he needs 
      //to switch to another database. But the Task is removed already the first time when going through PasswordActivity 
      // (with the wrong db).
      //Then after switching to the right database, the task is gone.

      //A reason this code existed was the following workflow:
      //Using Chrome browser (with NEW_TASK flag for ActionSend): Share URL -> KP2A.
      //Now the AppTask was in PasswordActivity and didn't get out of it.
      //This is now solved by returning new tasks in ActivityResult.

      //RemoveTaskFromIntent(act);
      //act.AppTask = new NullTask();
    }

    public override void PopulatePasswordAccessServiceIntent(Intent intent)
    {
      base.PopulatePasswordAccessServiceIntent(intent);
      intent.PutExtra(UrlToSearchKey, UrlToSearchFor);
    }

    public override void CompleteOnCreateEntryActivity(EntryActivity activity, Thread notifyPluginsOnOpenThread)
    {
      if (App.Kp2a.LastOpenedEntry != null)
        App.Kp2a.LastOpenedEntry.SearchUrl = UrlToSearchFor;


      //if the database is readonly (or no URL exists), don't offer to modify the URL
      if ((App.Kp2a.CurrentDb.CanWrite == false) || (String.IsNullOrEmpty(UrlToSearchFor) || keepass2android.ShareUrlResults.GetSearchResultsForUrl(UrlToSearchFor).Entries.Any(e => e == activity.Entry)))
      {
        base.CompleteOnCreateEntryActivity(activity, notifyPluginsOnOpenThread);
        return;
      }

      AskAddUrlThenCompleteCreate(activity, UrlToSearchFor, notifyPluginsOnOpenThread);
    }


    /// <summary>
    /// brings up a dialog asking the user whether he wants to add the given URL to the entry for automatic finding
    /// </summary>
    public void AskAddUrlThenCompleteCreate(EntryActivity activity, string url, Thread notifyPluginsOnOpenThread)
    {
      MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity);
      builder.SetTitle(activity.GetString(Resource.String.AddUrlToEntryDialog_title));

      builder.SetMessage(activity.GetString(Resource.String.AddUrlToEntryDialog_text, new Java.Lang.Object[] { url }));

      builder.SetPositiveButton(activity.GetString(Resource.String.yes), (dlgSender, dlgEvt) =>
      {
        activity.AddUrlToEntry(url, (EntryActivity thenActiveActivity) => base.CompleteOnCreateEntryActivity(thenActiveActivity, notifyPluginsOnOpenThread
));
      });

      builder.SetNegativeButton(activity.GetString(Resource.String.no), (dlgSender, dlgEvt) =>
      {
        base.CompleteOnCreateEntryActivity(activity, notifyPluginsOnOpenThread);
      });

      Dialog dialog = builder.Create();
      dialog.Show();
    }
  }

  public class OpenSpecificEntryTask : SelectEntryTask
  {
    public OpenSpecificEntryTask()
    {
    }

    public const String EntryUuidKey = "EntryUuid";

    public string EntryUuid
    {
      get;
      set;
    }

    public override void Setup(Bundle b)
    {
      base.Setup(b);
      EntryUuid = b.GetString(EntryUuidKey);

    }
    public override IEnumerable<IExtra> Extras
    {
      get
      {
        foreach (IExtra e in base.Extras)
          yield return e;

        yield return new StringExtra { Key = EntryUuidKey, Value = EntryUuid };
      }
    }

    public override void LaunchFirstGroupActivity(Activity act)
    {
      ShareUrlResults.Launch(act, this, new ActivityLaunchModeRequestCode(0));
    }

  }


  public enum ActivationCondition
  {
    Never,
    WhenTotp,
    Always
  }
  /// <summary>
  /// User is about to select an entry for use in another app
  /// </summary>
  public class SelectEntryTask : AppTask
  {
    public SelectEntryTask()
    {
      ShowUserNotifications = ActivationCondition.Always;
      CloseAfterCreate = true;
      ActivateKeyboard = ActivationCondition.Never;
      CopyTotpToClipboard = false;
    }

    public const String ShowUserNotificationsKey = "ShowUserNotifications";



    public ActivationCondition ShowUserNotifications { get; set; }

    public const String CloseAfterCreateKey = "CloseAfterCreate";
    public const String ActivateKeyboardKey = "ActivateKeyboard";
    public const String CopyTotpToClipboardKey = "CopyTotpToClipboard";

    public bool CloseAfterCreate { get; set; }
    public ActivationCondition ActivateKeyboard { get; set; }

    public bool CopyTotpToClipboard { get; set; }


    public override void Setup(Bundle b)
    {
      ShowUserNotifications = (ActivationCondition)GetIntFromBundle(b, ShowUserNotificationsKey, (int)ActivationCondition.Always);
      CloseAfterCreate = GetBoolFromBundle(b, CloseAfterCreateKey, true);
      ActivateKeyboard = (ActivationCondition)GetIntFromBundle(b, ActivateKeyboardKey, (int)ActivationCondition.Always);
      CopyTotpToClipboard = GetBoolFromBundle(b, CopyTotpToClipboardKey, false);
    }


    public override IEnumerable<IExtra> Extras
    {
      get
      {
        yield return new StringExtra { Key = ShowUserNotificationsKey, Value = ((int)ShowUserNotifications).ToString() };
        yield return new StringExtra { Key = CloseAfterCreateKey, Value = CloseAfterCreate.ToString() };
        yield return new StringExtra { Key = ActivateKeyboardKey, Value = ((int)ActivateKeyboard).ToString() };
        yield return new StringExtra { Key = CopyTotpToClipboardKey, Value = CopyTotpToClipboard.ToString() };
      }
    }

    public override void CompleteOnCreateEntryActivity(EntryActivity activity, Thread notifyPluginsOnOpenThread)
    {
      Context ctx = activity;
      if (ctx == null)
        ctx = LocaleManager.LocalizedAppContext;

      var pwEntryOutput = new PwEntryOutput(activity.Entry, App.Kp2a.CurrentDb);
      var totpPluginAdapter = new Kp2aTotp().TryGetAdapter(pwEntryOutput);
      bool isTotpEntry = totpPluginAdapter != null;

      bool activateKeyboard = ActivateKeyboard == ActivationCondition.Always || (ActivateKeyboard == ActivationCondition.WhenTotp && isTotpEntry);

      if ((ShowUserNotifications == ActivationCondition.Always)
          || ((ShowUserNotifications == ActivationCondition.WhenTotp) && isTotpEntry)
          || activateKeyboard)
      {
        //show the notifications
        activity.StartNotificationsService(activateKeyboard);
      }
      else
      {
        //to avoid getting into inconsistent state (LastOpenedEntry and Notifications): clear notifications:
        CopyToClipboardService.CancelNotifications(activity);
      }

      if (CopyTotpToClipboard && isTotpEntry)
      {
        DoCopyTotpToClipboard(activity, pwEntryOutput, totpPluginAdapter);
      }

      if (CloseAfterCreate)
      {
        //give plugins and TOTP time to do their work:
        notifyPluginsOnOpenThread.Join(TimeSpan.FromSeconds(1));
        //close
        activity.CloseAfterTaskComplete();
      }
    }

    private static void DoCopyTotpToClipboard(EntryActivity activity, PwEntryOutput pwEntryOutput,
        ITotpPluginAdapter? totpPluginAdapter)
    {
      Dictionary<string, string> entryFields = pwEntryOutput.OutputStrings.ToDictionary(pair => StrUtil.SafeXmlString(pair.Key), pair => pair.Value.ReadString());
      var totpData = totpPluginAdapter.GetTotpData(entryFields, activity, true);
      if (totpData.IsTotpEntry)
      {
        TOTPProvider prov = new TOTPProvider(totpData);
        string totp = prov.GenerateByByte(totpData.TotpSecret);
        CopyToClipboardService.CopyValueToClipboardWithTimeout(activity, totp, true);

        App.Kp2a.ShowMessage(activity, activity.GetString(Resource.String.TotpCopiedToClipboard),
            MessageSeverity.Info);
      }
    }
  }


  /// <summary>
  /// User is about to move entries and/or groups to another group
  /// </summary>
  public class MoveElementsTask : AppTask
  {
    public override bool CanActivateSearchViewOnStart
    {
      get { return false; }
      set { }
    }

    public const String UuidsKey = "MoveElement_Uuids";

    public IEnumerable<PwUuid> Uuids
    {
      get;
      set;
    }

    public override void Setup(Bundle b)
    {
      Uuids = b.GetString(UuidsKey).Split(';')
         .Where(s => !String.IsNullOrEmpty(s))
         .Select(stringPart => new PwUuid(MemUtil.HexStringToByteArray(stringPart)))
         .ToList(); //property might be accessed several times, avoid parsing each time
    }

    public override IEnumerable<IExtra> Extras
    {
      get
      {
        yield return new StringExtra
        {
          Key = UuidsKey,
          Value = Uuids.Select(uuid => MemUtil.ByteArrayToHexString(uuid.UuidBytes))
                .Aggregate((a, b) => a + ";" + b)
        };
      }
    }

    public override void StartInGroupActivity(GroupBaseActivity groupBaseActivity)
    {
      base.StartInGroupActivity(groupBaseActivity);
      groupBaseActivity.StartMovingElements();
    }
    public override void SetupGroupBaseActivityButtons(GroupBaseActivity groupBaseActivity)
    {
      groupBaseActivity.ShowInsertElementsButtons();
    }
  }


  /// <summary>
  /// User is about to create a new entry. The task might already "know" some information about the contents.
  /// </summary>
  public class CreateEntryThenCloseTask : AppTask
  {
    public CreateEntryThenCloseTask()
    {
      ShowUserNotifications = ActivationCondition.Always;
    }

    public override bool CanActivateSearchViewOnStart
    {
      get { return false; }
      set { }
    }

    /// <summary>
    /// extra key if only a URL is passed. optional.
    /// </summary>
    public const String UrlKey = "CreateEntry_Url";

    /// <summary>
    /// extra key if a json serialized key/value mapping is passed. optional.
    /// </summary>
    /// Uses the PluginSDKs keys because this is mainly used for communicating with plugins.
    /// Of course the data might also contain "non-output-data" (e.g. placeholders), but usually won't.
    public const String AllFieldsKey = Keepass2android.Pluginsdk.Strings.ExtraEntryOutputData;

    /// <summary>
    /// extra key to specify a list of protected field keys in AllFieldsKey. Passed as StringArrayExtra. optional.
    /// </summary>
    public const String ProtectedFieldsListKey = Keepass2android.Pluginsdk.Strings.ExtraProtectedFieldsList;


    /// <summary>
    /// Extra key to specify whether user notifications (e.g. for copy password or keyboard) should be displayed when the entry 
    /// is selected after creating.
    /// </summary>
    public const String ShowUserNotificationsKey = "ShowUserNotifications";


    public string Url { get; set; }

    public string AllFields { get; set; }

    public IList<string> ProtectedFieldsList { get; set; }

    public ActivationCondition ShowUserNotifications { get; set; }


    public override void Setup(Bundle b)
    {

      ShowUserNotifications = (ActivationCondition)GetIntFromBundle(b, ShowUserNotificationsKey, (int)ActivationCondition.Always);

      Url = b.GetString(UrlKey);
      AllFields = b.GetString(AllFieldsKey);
      ProtectedFieldsList = b.GetStringArrayList(ProtectedFieldsListKey);
    }
    public override IEnumerable<IExtra> Extras
    {
      get
      {
        if (Url != null)
          yield return new StringExtra { Key = UrlKey, Value = Url };
        if (AllFields != null)
          yield return new StringExtra { Key = AllFieldsKey, Value = AllFields };
        if (ProtectedFieldsList != null)
          yield return new StringArrayListExtra { Key = ProtectedFieldsListKey, Value = ProtectedFieldsList };

        yield return new StringExtra { Key = ShowUserNotificationsKey, Value = ShowUserNotifications.ToString() };
      }
    }


    public override void PrepareNewEntry(PwEntry newEntry)
    {
      if (Url != null)
      {
        Util.SetNextFreeUrlField(newEntry, Url);
      }
      if (AllFields != null)
      {

        var allFields = new Org.Json.JSONObject(AllFields);
        for (var iter = allFields.Keys(); iter.HasNext;)
        {
          string key = iter.Next().ToString();
          string value = allFields.Get(key).ToString();
          bool isProtected = ((ProtectedFieldsList != null) && (ProtectedFieldsList.Contains(key)))
              || (key == PwDefs.PasswordField);
          newEntry.Strings.Set(key, new ProtectedString(isProtected, value));
        }

      }

    }

    public override void AfterAddNewEntry(EntryEditActivity entryEditActivity, PwEntry newEntry)
    {
      EntryActivity.Launch(entryEditActivity, newEntry, -1,
          new SelectEntryTask() { ShowUserNotifications = this.ShowUserNotifications, ActivateKeyboard = ActivationCondition.Never },
          ActivityFlags.ForwardResult);
      //no need to call Finish here, that's done in EntryEditActivity ("closeOrShowError")	
    }

    public override void CompleteOnCreateEntryActivity(EntryActivity activity, Thread notifyPluginsOnOpenThread)
    {
      //if the user selects an entry before creating the new one, we're not closing the app
      base.CompleteOnCreateEntryActivity(activity, notifyPluginsOnOpenThread);
    }
  }


  /// <summary>
  /// Navigate to a folder and open a Task (appear in SearchResult)
  /// </summary>
  public abstract class NavigateAndLaunchTask : AppTask
  {
    public override bool CanActivateSearchViewOnStart
    {
      get { return false; }
      set { }
    }

    // All group Uuid are stored in guuidKey + indice
    // The last one is the destination group 
    public const String NumberOfGroupsKey = "NumberOfGroups";
    public const String GFullIdKey = "gFullIdKey";
    public const String FullGroupNameKey = "fullGroupNameKey";
    public const String ToastEnableKey = "toastEnableKey";

#if INCLUDE_DEBUG_MOVE_GROUPNAME
		public const String gNameKey = "gNameKey";
		private LinkedList<string> groupNameList;
#endif

    private LinkedList<string> _fullGroupIds;
    protected AppTask TaskToBeLaunchedAfterNavigation;

    protected String FullGroupName
    {
      get;
      set;
    }

    protected bool ToastEnable
    {
      get;
      set;
    }

    protected NavigateAndLaunchTask()
    {
      TaskToBeLaunchedAfterNavigation = new NullTask();
      FullGroupName = "";
      ToastEnable = false;
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="keepass2android.NavigateAndLaunchTask"/> class.
    /// </summary>
    /// <param name="groups">Groups.</param>
    /// <param name="taskToBeLaunchedAfterNavigation">Task to be launched after navigation.</param>
    /// <param name="toastEnable">If set to <c>true</c>, toast will be displayed after navigation.</param>
    protected NavigateAndLaunchTask(PwGroup groups, AppTask taskToBeLaunchedAfterNavigation, bool toastEnable = false)
    {
      TaskToBeLaunchedAfterNavigation = taskToBeLaunchedAfterNavigation;
      PopulateGroups(groups);
      ToastEnable = toastEnable;
    }

    public void PopulateGroups(PwGroup groups)
    {

      _fullGroupIds = new LinkedList<String>();

#if INCLUDE_DEBUG_MOVE_GROUPNAME
			groupNameList = new LinkedList<String>{};
#endif

      FullGroupName = "";
      PwGroup readGroup = groups;
      while (readGroup != null)
      {

        if ((readGroup.ParentGroup != null) ||
            (readGroup.ParentGroup == null) && (readGroup == groups))
        {
          FullGroupName = readGroup.Name + "." + FullGroupName;
        }

        _fullGroupIds.AddFirst(new ElementAndDatabaseId(App.Kp2a.FindDatabaseForElement(readGroup), readGroup).FullId);

#if INCLUDE_DEBUG_MOVE_GROUPNAME
				groupNameList.AddFirst (readGroup.Name);
#endif

        readGroup = readGroup.ParentGroup;

      }

    }


    /// <summary>
    /// Loads the parameters of the task from the given bundle. Embeded task is not setup from this bundle
    /// </summary>
    /// <param name="b">The bundle component.</param>
    public override void Setup(Bundle b)
    {
      int numberOfGroups = b.GetInt(NumberOfGroupsKey);

      _fullGroupIds = new LinkedList<String>();
#if INCLUDE_DEBUG_MOVE_GROUPNAME
			groupNameList = new LinkedList<String>{};
#endif

      int i = 0;

      while (i < numberOfGroups)
      {

        _fullGroupIds.AddLast(b.GetString(GFullIdKey + i.ToString(CultureInfo.InvariantCulture)));

#if INCLUDE_DEBUG_MOVE_GROUPNAME
				groupNameList.AddLast ( b.GetString (gNameKey + i);
#endif
        i++;
      }

      FullGroupName = b.GetString(FullGroupNameKey);
      ToastEnable = b.GetBoolean(ToastEnableKey);

    }

    public override IEnumerable<IExtra> Extras
    {
      get
      {
        // Return Navigate group Extras


#if INCLUDE_DEBUG_MOVE_GROUPNAME
				IEnumerator<String> eGroupName = groupNameList.GetEnumerator ();
#endif

        int i = 0;
        foreach (var fullGroupId in _fullGroupIds)
        {
          yield return new StringExtra { Key = GFullIdKey + i.ToString(CultureInfo.InvariantCulture), Value = fullGroupId };

#if INCLUDE_DEBUG_MOVE_GROUPNAME
					eGroupName.MoveNext();
					yield return new StringExtra { Key = gNameKey + i.ToString (), Value = eGroupName.Current };
#endif

          i++;
        }

        yield return new IntExtra { Key = NumberOfGroupsKey, Value = i };
        yield return new StringExtra { Key = FullGroupNameKey, Value = FullGroupName };
        yield return new BoolExtra { Key = ToastEnableKey, Value = ToastEnable };

        // Return afterTaskExtras
        foreach (var extra in TaskToBeLaunchedAfterNavigation.Extras)
        {
          yield return extra;
        }

      }
    }

    public override void StartInGroupActivity(GroupBaseActivity groupBaseActivity)
    {
      base.StartInGroupActivity(groupBaseActivity);

      if (GroupIsFound(groupBaseActivity))
      { // Group has been found: display toaster and stop here

        if (ToastEnable)
        {
          String toastMessage = groupBaseActivity.GetString(Resource.String.NavigationToGroupCompleted_message, new Java.Lang.Object[] { FullGroupName });

          App.Kp2a.ShowMessage(groupBaseActivity, toastMessage, MessageSeverity.Info);
        }

        groupBaseActivity.StartTask(TaskToBeLaunchedAfterNavigation);
        return;

      }
      else if ((groupBaseActivity.FullGroupId != null) && _fullGroupIds.Contains(groupBaseActivity.FullGroupId.FullId))
      { // Need to down up in groups tree

        // Get next Group Uuid
        var linkedListNode = _fullGroupIds.Find(groupBaseActivity.FullGroupId.FullId);
        if (linkedListNode != null)
        {
          //Note: Resharper says there is a possible NullRefException.
          //This is not the case because it was checked above if we're already there or not.
          String nextGroupFullId = linkedListNode.Next.Value;

          ElementAndDatabaseId fullId = new ElementAndDatabaseId(nextGroupFullId);

          PwUuid nextGroupPwUuid = new PwUuid(MemUtil.HexStringToByteArray(fullId.ElementIdString));

          // Create Group Activity
          PwGroup nextGroup = App.Kp2a.GetDatabase(fullId.DatabaseId).GroupsById[nextGroupPwUuid];
          GroupActivity.Launch(groupBaseActivity, nextGroup, this, new ActivityLaunchModeRequestCode(0));
        }
        return;

      }
      else
      { // Need to go up in groups tree
        ElementAndDatabaseId fullId = new ElementAndDatabaseId(_fullGroupIds.Last.Value);
        var targetDb = App.Kp2a.GetDatabase(fullId.DatabaseId);
        if (App.Kp2a.CurrentDb != targetDb)
        {
          App.Kp2a.CurrentDb = targetDb;
          GroupActivity.Launch(groupBaseActivity, targetDb.Root, this, new ActivityLaunchModeForward());
        }
        else
        {
          SetActivityResult(groupBaseActivity, KeePass.ExitNormal);
        }
        groupBaseActivity.Finish();


      }

    }
    public override void SetupGroupBaseActivityButtons(GroupBaseActivity groupBaseActivity)
    {

    }

    public bool GroupIsFound(GroupBaseActivity groupBaseActivity)
    {
      var fullId = groupBaseActivity.FullGroupId;
      return fullId != null && _fullGroupIds.Last.Value.Equals(fullId.FullId);
    }
  }

  public class NavigateToFolder : NavigateAndLaunchTask
  {

    public NavigateToFolder()
    {

    }


    public NavigateToFolder(Database db, PwGroup groups, bool toastEnable = false)
        : base(groups, new NullTask(), toastEnable)
    {
    }

  }

  public class NavigateToFolderAndLaunchMoveElementTask : NavigateAndLaunchTask
  {


    public NavigateToFolderAndLaunchMoveElementTask()
    {

    }

    public NavigateToFolderAndLaunchMoveElementTask(Database db, PwGroup groups, List<PwUuid> uuids, bool toastEnable = false)
        : base(groups, new MoveElementsTask() { Uuids = uuids }, toastEnable)
    {
    }

    public override void Setup(Bundle b)
    {
      base.Setup(b);

      TaskToBeLaunchedAfterNavigation = new MoveElementsTask();
      TaskToBeLaunchedAfterNavigation.Setup(b);

    }


  }

}

